#include "d3d_particles.h"
#include "d3d.h"
#include "..\engine\engine.h"
#include "..\engine\error.h"
#include "..\math\math.h"
#include "..\engine\memorymanager.h"

void DefaultSpawnFunction(D3DParticleSystem* system, Particle* pParticle)
{
	EE_ZEROMEMORY(pParticle, sizeof(Particle)); 
	pParticle->velocity[0] = ((float)((rand() % 10) - 5)); // * 0.1f; 
	pParticle->velocity[1] = ((float)((rand() % 10) - 5)) *10 ; // * 0.1f; 
	pParticle->velocity[2] = ((float)((rand() % 10) - 5)); // * 0.1f; 
	pParticle->totallife = 1.0f; 
	pParticle->radius = 1000.0f; 
	pParticle->color[0] = 1.0f; 
	pParticle->color[1] = 1.0f; 
	pParticle->color[2] = 1.0f; 
	pParticle->acceleration[1] = -9.8f; 
}

string D3DParticleSystem::GetType() const
{
	return string("D3DParticleSystem"); 
}

HRESULT D3DParticleSystem::PreRender()
{
	Texture* pTexture = getTexture(0);
	LPDIRECT3DDEVICE9 m_pD3DDevice = GetDevice()->GetDevice(); 
	//TODO: shift m_pParticleDeclaration into object
	//GetDevice()->SetCurrentVertexDeclaration(GetDevice()->m_pParticleDeclaration); 
	//m_pD3DDevice->SetStreamSource(0, pVertexBuffer, 0, Particle::size); 
	GetDevice()->SetTexture(0,pTexture); 

	return S_OK;
}

HRESULT D3DParticleSystem::PostRender()
{

	return S_OK;
}

//(CD3D9Device* pDevice) : IResource(pDevice)
D3DParticleSystem::D3DParticleSystem(CD3D9Device* pRenderDevice) : D3DRenderable(pRenderDevice)  
{	
	m_Identity = "ParticleSystem"; 
	m_pTextureUnitA = NULL; 
	m_Count=0;
	m_EmitCount = 1;
	mParticles = NULL;
	pVertexBuffer = NULL;
	m_NumPrimitives = 0;
	m_EmitTimer=0.0f;
	mpSpawnFunc = DefaultSpawnFunction;
}
HRESULT D3DParticleSystem::Reload()
{
	//TODO:
	return S_OK; 
}
void D3DParticleSystem::setSpawnFunction(void (*pFunc) (D3DParticleSystem*, Particle*))
{
	mpSpawnFunc = pFunc;
}
void D3DParticleSystem::setCount(const unsigned int count) {

	const unsigned int newCount = MAX(0, count); 
	Particle* newParticles = NULL; 

	if(newCount == 0) {
		mParticles = NULL; 
	}
	else {
		
		newParticles = EE_NEW Particle[newCount]; 
		const unsigned int aux = (newCount < m_Count) ? newCount : m_Count; 

		if(mParticles){
			memcpy(newParticles, mParticles, Particle::size * aux); 
			EE_SAFEDELETEARRAY(mParticles); 
		}else
			EE_ZEROMEMORY(newParticles, Particle::size * aux); 

		mParticles = newParticles; 
	}

	m_Count = newCount; 

	if(pVertexBuffer)
		pVertexBuffer->Release(); 

	if(SUCCEEDED(GetDevice()->m_pD3DDevice->CreateVertexBuffer(m_Count * Particle::size,
		D3DUSAGE_WRITEONLY,0,D3DPOOL_DEFAULT, &pVertexBuffer,NULL))) {

		void* pTmpVertices = NULL; 
		if(SUCCEEDED(pVertexBuffer->Lock(0, Particle::size * m_Count, (void **) &pTmpVertices, 0))) { 
		
			EE_ZEROMEMORY(pTmpVertices, m_Count * Particle::size); 
			memcpy(pTmpVertices, mParticles, Particle::size * m_Count); 
			pVertexBuffer->Unlock(); 
		}
	}
	else {
		pVertexBuffer = NULL; 
	}
}

D3DParticleSystem::~D3DParticleSystem(){}

bool D3DParticleSystem::QueryEmit(float dt)
{
	if(m_Frequency == 0.0f)
		return true; 

	if(m_EmitTimer > (1.0f / m_Frequency))
	{
		m_EmitTimer = dt;  
		return true; 
	}

	m_EmitTimer += dt; 
	return false; 
}

void D3DParticleSystem::setTexture(Texture* pTexture, const unsigned int unit)
{
	if(unit == 0)
		m_pTextureUnitA = pTexture;
}

Texture* D3DParticleSystem::getTexture(const unsigned int idx)
{
	return (idx == 0) ? m_pTextureUnitA : NULL;
}

void D3DParticleSystem::Render(const unsigned int vbOffset)
{
	GetDevice()->GetDevice()->DrawPrimitive(D3DPT_POINTLIST, vbOffset, m_NumPrimitives);

}
unsigned int D3DParticleSystem::getVertexCount() const
{
	return m_NumPrimitives;
}
void* D3DParticleSystem::getVertices()
{
	return mParticles;
}
void D3DParticleSystem::setEmitCount(const unsigned int count)
{
	m_EmitCount = (count == 0) ? 1 : count;
}
void D3DParticleSystem::setEmitFrequency(const float frequency)
{
	m_Frequency = (frequency == 0.0f) ? 0.0f : frequency;
}
void D3DParticleSystem::Draw() {
	m_pShader->AddRenderQue(this, mTransform);
}
void D3DParticleSystem::ResetParticles()
{
	for(unsigned int i = 0;  i < m_NumPrimitives;  i++) {

		mParticles[i].life = 0.0f;
	}
}
void D3DParticleSystem::Update(float dt) {

	if(QueryEmit(dt) && m_NumPrimitives < m_Count) {
	
		unsigned int freeParticles = m_Count - m_NumPrimitives; 

		const unsigned int emit = (freeParticles > 0) ? MIN(m_EmitCount, m_Count - m_NumPrimitives) : 0;
		
		const unsigned int initalPrimitiveCount = m_NumPrimitives;

		for(unsigned int i = initalPrimitiveCount;  i < initalPrimitiveCount+ emit;  i++)
		{
			mpSpawnFunc(this,&mParticles[i]); 
			m_NumPrimitives++; 
		}
	
	
	}

	for(unsigned int i = 0;  i < m_NumPrimitives;  i++) {

		if(mParticles[i].life > mParticles[i].totallife) {
			mParticles[i] = mParticles[m_NumPrimitives - 1]; 	//if this particle just died, replace it with a known live one
			mParticles[m_NumPrimitives - 1] = Particle(); 

			m_NumPrimitives--; 
		}
		else {
			mParticles[i].life += dt; 
		}
	}

	//Clear out the end of the list (dead particles)
//TODO: (m_Count - m_NumPrimitives) < 0 causes crash
	EE_ZEROMEMORY(&mParticles[m_NumPrimitives], sizeof(Particle) * (m_Count - m_NumPrimitives)); 
} 

HRESULT CD3D9Device::CreateParticleSystem(D3DParticleSystem** pOut,Texture *pTexture)
{
	D3DParticleSystem* pSystem = NULL; 
	D3DShader* pShader = NULL;
	IResource *pFindTexture = NULL;

	try{
		if(ISNULL(pTexture) || ISNULL(pOut))
			throw EE_EXCEPTION("Bad parameters.");

		if(FAILED(SearchForResource(pTexture->Identity(), &pFindTexture, CD3D9Device::RESOURCE_TEXTURE)) || pFindTexture != pTexture)
			throw EE_EXCEPTION("Texture not loaded.");

		if(FAILED(CreateShader((D3DShader**)&pShader, "ParticleSystem.fx")))
			throw EE_EXCEPTION("Failed to Create default particle shader"); 

		pSystem = EE_NEW D3DParticleSystem(this); 
		pSystem->setShader(pShader);
		pSystem->setTexture(pTexture, 0);
		pSystem->m_ElementSize = Particle::size;
		pSystem->SetTransform(Matrix4f::identity());

		m_ParticleSystems.Add(pSystem); 
		*pOut = pSystem; 
		return S_OK; 
	}
	catch(CError::CErrorData error)
	{
		CError::CError(error, ERRORREPORT_LOG);
		EE_SAFEDELETE(pSystem); 
		return E_FAIL; 
	}
}

HRESULT CD3D9Device::CreateParticleSystem(D3DParticleSystem** pOut,const string& pTextureFilename)
{
	D3DParticleSystem* pSystem = NULL; 
	Texture* pTexture = NULL; 
	D3DShader* pShader = NULL;
	try{
		if(FAILED(CreateTexture(pTextureFilename,&pTexture)))
			throw EE_EXCEPTION("Failed to Create particle system textures"); 
		
		if(FAILED(CreateShader((D3DShader**)&pShader, "ParticleSystem.fx")))
			throw EE_EXCEPTION("Failed to Create default particle shader"); 

		pSystem = EE_NEW D3DParticleSystem(this); 
		pSystem->setShader(pShader);
		pSystem->setTexture(pTexture, 0);
		pSystem->m_ElementSize = Particle::size;
		pSystem->SetTransform(Matrix4f::identity());

//		pSystem->mTexture = pTexture; 
		m_ParticleSystems.Add(pSystem); 
		*pOut = pSystem; 
		return S_OK; 
	}
	catch(CError::CErrorData error)
	{
		CError::CError(error, ERRORREPORT_LOG);
		EE_SAFEDELETE(pSystem); 
		return E_FAIL; 
	}
}


HRESULT D3DParticleSystem::Release()
{
	m_IsValid = false; 
	GetDevice()->DeregisterResource(this); 

	mTransform = Matrix4f::identity(); 
	EE_SAFEDELETEARRAY(mParticles); 
	m_pTextureUnitA = NULL; 
	mpSpawnFunc = &DefaultSpawnFunction; 
	m_Count = 0; 
	m_EmitTimer = 0.0f; 
	m_Frequency = 1.0f; 
	m_pShader->FlushRenderList();
	return S_OK; 
}
